package com.corticon.dialogs.web.designer;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;

import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.ModelMap;
import org.springframework.util.Assert;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;

import com.corticon.dialogs.dao.GenericDAO;
import com.corticon.dialogs.dao.LocaleDAO;
import com.corticon.dialogs.dao.UserGroupDAO;
import com.corticon.dialogs.model.*;
import com.corticon.dialogs.util.BeanSupport;
import com.corticon.dialogs.util.Pager;
import com.corticon.dialogs.util.security.CustomUserDetails;
import com.corticon.dialogs.util.security.SecurityUtils;
import com.corticon.dialogs.validation.GenericHibernateValidator;
import com.corticon.dialogs.validation.ValidationMessageFormatter;
import com.corticon.dialogs.web.BaseController;

/**
 * @author Lifan Yang
 */
@Controller
@SessionAttributes({"locale", "groups", "countries", "languages", "dataValidations"})
public class LocaleController extends BaseController {

    private final String DAO_SUFFIX = "DAO";
    private final String LIST_VIEW_NAME = "designer.locale.list";

    // DAOs
    @Resource
    private LocaleDAO localeDAO;
    @Resource
    private UserGroupDAO userGroupDAO;

    // Validators
    private CreateValidator createValidator;
    private EditValidator editValidator;

    // Other Resouces
    @Resource
    private BeanSupport beanSupport;

    private class CreateValidator extends GenericHibernateValidator<Locale> {

        public CreateValidator(ValidationMessageFormatter validationMessageFormatter) {
            super.validationMessageFormatter = validationMessageFormatter;
        }

        @Override
        public void complexValidate(Object object, Errors errors) {
            Locale locale = (Locale) object;

            // Country Id cannot be empty.
            if (StringUtils.isEmpty(locale.getCountry().getCode())) {
                errors.rejectValue("country", "validate.locale.id.countryId.notEmpty", "");
            }

            // Language Id cannot be empty.
            if (StringUtils.isEmpty(locale.getLanguage().getCode())) {
                errors.rejectValue("language", "validate.locale.id.languageId.notEmpty", "");
            }

            if (localeDAO.findById(locale.getCode()) != null) {
                errors.rejectValue("code", "lookup.code.isExisted",
                        "This country and language combination is already existed.");
            }
        }
    }

    private class EditValidator extends GenericHibernateValidator<Locale> {

        public EditValidator(ValidationMessageFormatter validationMessageFormatter) {
            super.validationMessageFormatter = validationMessageFormatter;
        }
    }

    @PostConstruct
    public void buildValidators() {
        createValidator = new CreateValidator(validationMessageFormatter);
        editValidator = new EditValidator(validationMessageFormatter);
    }

    @Transactional(readOnly = true)
    @RequestMapping(value = LIST_REQUEST, method = RequestMethod.GET)
    public String list(@RequestParam(required = false) Integer page, HttpServletRequest request) {

        Pager pager = constructPager(page, request);
        // Add sort infos
        pager = setPagerSort(pager, "code", request);
        
        List<Locale> locales = new ArrayList<Locale>();

        List<String> permissions = SecurityUtils.getCurrentUserPermissions();

        List<String> groupNames = new ArrayList<String>();

        if (permissions.contains(PermissionType.SYS_ADMIN.value())) {
            pager.setNumOfRecords(localeDAO.getCountAll());
            locales = localeDAO.getPageOfDataAll(pager);

            // Find all of groups in the system.
            List<UserGroup> groups = userGroupDAO.findAll();

            // Add the group name into the groupNames list.
            for (UserGroup group : groups) {
                groupNames.add(group.getName());
            }
        } else if (permissions.contains(PermissionType.PORTAL_USER.value())) {
            Locale localeExample = new Locale();
            localeExample.setUserGroup(new UserGroup(BasicGroup.ALL.toString()));

            pager.setNumOfRecords(localeDAO.getCountByExample(localeExample));
            locales = localeDAO.getPageOfDataByExample(localeExample, pager);

            // Add the group 'ALL' into the groupNames list.
            groupNames.add(BasicGroup.ALL.toString());
        } else {
            // This lookup example is used to load the lookups with the group 'ALL'.
            Locale localeGroupAllExample = new Locale();
            localeGroupAllExample.setUserGroup(new UserGroup(BasicGroup.ALL.toString()));

            // Get the current user.
            CustomUserDetails currentUser = SecurityUtils.getCurrentUser();

            // This lookup example is used to load the lookups with the current user group.
            Locale localeCurrentGroupExample = new Locale();
            localeCurrentGroupExample.setUserGroup(new UserGroup(currentUser.getGroupName()));

            Locale[] unionExamples = {localeGroupAllExample, localeCurrentGroupExample};

            pager.setNumOfRecords(localeDAO.getCountByUnionExamples(unionExamples));
            locales = localeDAO.getPageOfDataByUnionExamples(unionExamples, pager);

            // Add the group 'ALL' and the group of the current user into the groupNames list.
            groupNames.add(BasicGroup.ALL.toString());
            groupNames.add(currentUser.getGroupName());
        }

        pager = setPagerSort(pager, "code", request);
        request.setAttribute("pager", pager);
        request.setAttribute("order", request.getParameter("sortBy"));
        request.setAttribute("column", request.getParameter("sortColumn"));

        request.setAttribute("groups", groupNames);
        request.setAttribute("locales", locales);
        request.setAttribute(PAGER_NAME, pager);
        if (locales != null && locales.size() > 0) {
            request.setAttribute("foundMessage", getMessage(String.valueOf(locales.size()),
                    publicFoundMessage));
        } else {
            request.setAttribute("foundMessage", getMessage("", publicNotFoundMessage));
        }
        readMessages(defaultMessageKeys, request);

        return getViewNameByRequestURI(request);
    }

    @Transactional(readOnly = true)
    @RequestMapping(value = VIEW_REQUEST, method = RequestMethod.GET)
    public String view(@RequestParam String code, HttpServletRequest request) {

        Locale locale = localeDAO.findById(code);

        if (locale == null) {
            // Add messages.
            return entityNotFoundViewName;
        }

        if (!checkGroupPermission(locale)) {
            return redirectTo403();
        }

        request.setAttribute("locale", locale);

        return getViewNameByRequestURI(request);
    }

    /**
     * Enter the page for the create of Locale.
     *
     * @param request
     * @return
     */
    @Transactional(readOnly = true)
    @RequestMapping(value = CREATE_REQUEST, method = RequestMethod.GET)
    public String getCreate(ModelMap model, HttpServletRequest request) {

        setCreate(request);

        loadEntityListForLocale(model, request);

        Locale locale = Locale.newInstance();

        model.addAttribute("locale", locale);

        request.setAttribute(DEFAULT_MESSAGE_KEY, getMessage("lookup.create.message"));

        readMessages(defaultMessageKeys, request);

        return getViewNameByRequestURI(request);
    }

    @Transactional
    @RequestMapping(value = CREATE_REQUEST, method = RequestMethod.POST)
    public String postCreate(@ModelAttribute Locale locale, Errors errors,
                             HttpServletRequest request, SessionStatus sessionStatus) {

        String submitAction = request.getParameter("submitAction");

        setCreate(request);

        Map<String, List<String>> errorMessages = createValidator.invokeValidate(locale,
                errors, request.getLocale());

        if (errors.hasErrors()) {
            request.setAttribute("errors", errorMessages);
            return getViewNameByRequestURI(request);
        }

        // TODO: Make these work automatically.
        setNullAssociations(locale);

        setModifiedByInfo(locale);

        localeDAO.save(locale);

        sessionStatus.setComplete();

        // TODO: add messages.
        addMessages("message=lookup.create.success", request, messageSource, null);

        return redirectBySubmitAction(submitAction, "code=" + locale.getCode(), request);
    }

    @Transactional(readOnly = true)
    @RequestMapping(value = EDIT_REQUEST, method = RequestMethod.GET)
    public String getEdit(@RequestParam String code, ModelMap model,
                          HttpServletRequest request) {

        setEdit(request);

        Locale locale = localeDAO.findById(code);

        if (locale == null) {
            // TODO: Add messages.
            return entityNotFoundViewName;
        }

        if (locale.getUserGroup() != null && BasicGroup.ALL.toString().equals(locale.getUserGroup().
                getName()) && SecurityUtils.getCurrentUserPermissions().contains(
                PermissionType.GROUP_ADMIN.value())) {
            return redirectTo403();
        }else{
            CustomUserDetails currentUser = SecurityUtils.getCurrentUser();
		    User user = userDAO.findById(currentUser.getUsername());           
            if (user.getUserGroup() != locale.getModifiedBy().getUserGroup()
                    && SecurityUtils.getCurrentUserPermissions().contains(
                    PermissionType.GROUP_ADMIN.value())){
                return redirectTo403();
            }

        }

        locale.init();
        model.addAttribute("locale", locale);

        loadEntityListForLocale(model, request);

        request.setAttribute(DEFAULT_MESSAGE_KEY, getMessage("lookup.edit.message"));

        readMessages(defaultMessageKeys, request);

        return getViewNameByRequestURI(request);
    }

    @Transactional
    @RequestMapping(value = EDIT_REQUEST, method = RequestMethod.POST)
    public String postEdit(@ModelAttribute Locale locale, Errors errors, HttpServletRequest request,
                           SessionStatus sessionStatus) {

        String submitAction = (String) request.getParameter(SUBMIT_ACTION);

        setEdit(request);

        Map<String, List<String>> errorMessages = editValidator.invokeValidate(locale, errors,
                request.getLocale());

        if (errors.hasErrors()) {
            request.setAttribute(ERROR_MESSAGE_KEY, errorMessages);
            return getViewNameByRequestURI(request);
        }

        setNullAssociations(locale);

        setModifiedByInfo(locale);

        localeDAO.update(locale);

        sessionStatus.setComplete();

        // add messages.
        addMessages("message=lookup.edit.success", request, messageSource, null);

        return redirectBySubmitAction(submitAction, "code=" + locale.getCode(), request);
    }

    @Transactional(readOnly = true)
    @RequestMapping(method = RequestMethod.GET)
    public String search(@RequestParam(required = false) String keywords, @RequestParam(required = false) String groupName,
                         @RequestParam(required = false) Integer page, HttpServletRequest request) {

        // Get the current user.
        CustomUserDetails currentUser = SecurityUtils.getCurrentUser();
        List<String> permissions = currentUser.getPermissions();

        // Get the groups name should be searched according to the permission of the current user.
        List<String> groupNames = new ArrayList<String>();
        // If the user is not a sys admin and a portal user.
        if (!permissions.contains(PermissionType.SYS_ADMIN.value()) && !permissions.contains(
                PermissionType.PORTAL_USER.value())) {
            if (StringUtils.isEmpty(groupName)) {
                groupNames.add(BasicGroup.ALL.toString());
                groupNames.add(currentUser.getGroupName());
            } else {
                groupNames.add(groupName);
            }
        } else if (permissions.contains(PermissionType.PORTAL_USER.value())) {
            groupNames.add(BasicGroup.ALL.toString());
        } else {
            // If the current user is a sys admin.
            if (StringUtils.isNotEmpty(groupName)) {
                groupNames.add(groupName);
            }
        }

        // Construct the pager object and set the pager into the request scope.
        Pager pager = constructPager(page, request);
        // Add sort infos
        pager = setPagerSort(pager, "code", request);
        
        pager.setNumOfRecords(localeDAO.getCountForSearch(keywords, groupNames));
        pager.setRequestParameters("keywords=" + keywords + "&groupName=" + groupName);
        request.setAttribute("pager", pager);
        request.setAttribute("order", request.getParameter("sortBy"));
		request.setAttribute("column", request.getParameter("sortColumn"));

        // To search, then set the results into the request scope.
        List<Locale> results = new ArrayList<Locale>();
        results = localeDAO.search(keywords, groupNames, pager);
        request.setAttribute("locales", results);

        // Get the group list, then set it into the request scope.
        if (permissions.contains(PermissionType.SYS_ADMIN.value())) {
            // Find all of groups in the system.
            List<UserGroup> groups = userGroupDAO.findAll();

            // Add the group name into the groupNames list.
            for (UserGroup group : groups) {
                groupNames.add(group.getName());
            }
        } else if (permissions.contains(PermissionType.PORTAL_USER.value())) {
            // Add the group 'ALL' into the groupNames list.
            groupNames.add(BasicGroup.ALL.toString());

            // Portal User can only search the lookups in the group 'ALL'.
            if (!BasicGroup.ALL.toString().equals(groupName)) {
                return redirectTo403();
            }
        } else {
            groupNames.clear();
            // Get the current user.
            // Add the group 'ALL' and the group of the current user into the groupNames list.
            groupNames.add(BasicGroup.ALL.toString());
            groupNames.add(currentUser.getGroupName());

            // Other users can only search the lookups in the group 'ALL' or the same group with him.
            if (StringUtils.isNotEmpty(groupName) && !groupNames.contains(groupName)) {
                return redirectTo403();
            }
        }
        if (results != null && results.size() > 0) {
            request.setAttribute("foundMessage", getMessage(String.valueOf(results.size()),
                    publicFoundMessage));
        } else {
            request.setAttribute("foundMessage", getMessage("", publicNotFoundMessage));
        }
        request.setAttribute("groups", groupNames);

        return LIST_VIEW_NAME;
    }

    // ********** Helper Methods ********** //
    /**
     * TODO: Refactor.
     *
     * @param <T>
     * @param entity
     * @return
     */
    private <T> GenericDAO loadDAO(Class<T> entity) {
        String entityName = StringUtils.uncapitalize(entity.getSimpleName());
        String daoName = entityName + DAO_SUFFIX;
        return (GenericDAO) beanSupport.findBeanByName(daoName);
    }

    /**
     * Load the corresponding entity list according to the entity param and the currentAdmin param.
     *
     * @param <T>
     * @param
     * @return
     */
    private <T> List<T> getModelListByGroup(Class<T> entityClass, CustomUserDetails currentAdmin) {

        List<T> entityList = new ArrayList<T>();
        List<String> permissions = SecurityUtils.getCurrentUserPermissions();
        CustomUserDetails currentUser = SecurityUtils.getCurrentUser();

        GenericDAO dao = loadDAO(entityClass);
        if (permissions.contains(PermissionType.SYS_ADMIN.value())) {
            entityList = dao.findAll();
        } else if (permissions.contains(PermissionType.GROUP_ADMIN.value())) {

            if (LookupBase.class.isAssignableFrom(entityClass)) {

                LookupBase lookupExample = (LookupBase) BeanUtils.instantiateClass(entityClass);
                lookupExample.setUserGroup(new UserGroup(currentUser.getGroupName()));

                List<T> list = (List<T>) dao.findByExample(lookupExample);
                if (list != null && list.size() > 0) {
                    entityList.addAll(list);
                }

                lookupExample.setUserGroup(new UserGroup(BasicGroup.ALL.toString()));
                List<T> listAll = (List<T>) dao.findByExample(lookupExample);

                if (listAll != null && listAll.size() > 0) {
                    entityList.addAll(listAll);
                }
            }

            if (entityClass.isAssignableFrom(UserGroup.class)) {

                T entity = (T) dao.findById(currentAdmin.getGroupName());
                entityList.add(entity);
            }
        }

        return entityList;
    }

    /**
     * Load the entities (country, language, datavalidation and so on) list into {@link ModelMap}
     * for the locale create edit operations.
     *
     * @param model
     */
    private void loadEntityListForLocale(ModelMap model, HttpServletRequest request) {

        CustomUserDetails currentAdmin = SecurityUtils.getCurrentUser();

        if (isCreate(request)) {
            // Read the list of the countrys and the languages.
            List<Country> countries = getModelListByGroup(Country.class, currentAdmin);
            model.addAttribute("countries", countries);

            List<Language> languages = getModelListByGroup(Language.class, currentAdmin);
            model.addAttribute("languages", languages);
        }

        List<UserGroup> groups = getModelListByGroup(UserGroup.class, currentAdmin);
        model.addAttribute("groups", groups);

        List<DataValidation> dataValidations = getModelListByGroup(DataValidation.class,
                currentAdmin);
        model.addAttribute("dataValidations", dataValidations);
    }

//    private String redirectBySubmitAction(String submitAction, String countryId, String languageId) {
//
//        Assert.notNull(submitAction);
//
//        String countryIdKey = "countryId=";
//        String languageIdKey = "languageId=";
//
//        if (SUBMIT.equalsIgnoreCase(submitAction)) {
//            return redirectPrefix + VIEW_REQUEST + requestSuffix + "?" + countryIdKey +
//                countryId + "&" + languageIdKey + languageId;
//        }
//        if (SUBMIT_AND_NEW.equalsIgnoreCase(submitAction)) {
//            return redirectPrefix + CREATE_REQUEST + requestSuffix;
//        }
//        if (SUBMIT_AND_EXIT.equalsIgnoreCase(submitAction)) {
//            return redirectPrefix + LIST_REQUEST + requestSuffix;
//        }
//
//        return null;
//    }

    /**
     * Check whether the current user has the permission to access the lookup.
     *
     * @return
     */
    private boolean checkGroupPermission(Locale locale) {
        List<String> permissions = SecurityUtils.getCurrentUserPermissions();
        if (permissions.contains(PermissionType.SYS_ADMIN.value())) {
            return true;
        } else {
            CustomUserDetails currentUser = SecurityUtils.getCurrentUser();
            if (locale.getUserGroup().getName().equals(BasicGroup.ALL.toString()) || currentUser.
                    getGroupName().equals(locale.getUserGroup().getName())) {
                return true;
            } else {
                return false;
            }
        }
    }

    /**
     * TODO: Make this method commonly.
     *
     * @param locale
     */
    private void setNullAssociations(Locale locale) {
        if (StringUtils.isEmpty(locale.getDateMask().getCode())) {
            locale.setDateMask(null);
        }
        if (StringUtils.isEmpty(locale.getDatetimeMask().getCode())) {
            locale.setDatetimeMask(null);
        }
        if (StringUtils.isEmpty(locale.getTimeMask().getCode())) {
            locale.setTimeMask(null);
        }
    }
}